﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using LowLevelGraphics.Drawing;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// 
    /// </summary>
    public enum SIMPLE_TEMPLATE_MATCHING_MODE
    {
        /// <summary>
        /// 
        /// </summary>
        NORMAL,
        /// <summary>
        /// 
        /// </summary>
        PRECACHED,
        /// <summary>
        /// 
        /// </summary>
        MOST_EXACT_GRAY_MATCH,
        /// <summary>
        /// 
        /// </summary>
        MOST_EXACT_COLORED_MATCH
    }

    /// <summary>
    /// 
    /// </summary>
    public struct SADPosition
    {
        public int BestRow;
        public int BestCol;
        public double BestSAD;

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("BestRow: {0}, BestCol: {1}, BestSAD: {2}", BestRow, BestCol, BestSAD);
        }
    }

    /// <summary>
    /// This is an algorithm used to find an object
    /// </summary>
    public class SimpleTemplateMatching : BaseImageFilter
    {
        protected UnsafeBitmap m_BitmapTemplate = null;
        protected bool m_bPrecache = false;
        protected int m_ScaleDown = 0;
        protected SIMPLE_TEMPLATE_MATCHING_MODE m_Matching_Mode = SIMPLE_TEMPLATE_MATCHING_MODE.MOST_EXACT_GRAY_MATCH;
        protected int m_nTolerance = 225;

        protected List<SADPosition> aPosition = new List<SADPosition>();
        
        /// <summary>
        /// Initializes a new instance of the <see cref="SimpleTemplateMatching"/> class.
        /// </summary>        
        public SimpleTemplateMatching()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SimpleTemplateMatching"/> class.
        /// </summary>
        /// <param name="_bitmapTemplate">The _bitmap template.</param>
        public SimpleTemplateMatching(UnsafeBitmap _bitmapTemplate)
        {
            m_BitmapTemplate = _bitmapTemplate;
            m_szStep = new Size(1, 1);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SimpleTemplateMatching"/> class.
        /// </summary>
        /// <param name="_simpleTemplateMatching">The _simple template matching.</param>
        internal SimpleTemplateMatching(SimpleTemplateMatching _simpleTemplateMatching)
        {
        }

        /// <summary>
        /// Gets or sets the tolerance.
        /// </summary>
        /// <value>The tolerance.</value>
        public int Tolerance
        {
            get { return m_nTolerance; }
            set { m_nTolerance = value; }
        }

        /// <summary>
        /// Gets the bitmap template.
        /// </summary>
        /// <value>The bitmap template.</value>
        public UnsafeBitmap BitmapTemplate
        {
            get { return m_BitmapTemplate; }
            set { m_BitmapTemplate = value; }
        }

        /// <summary>
        /// Gets or sets the matching mode.
        /// </summary>
        /// <value>The matching mode.</value>
        public SIMPLE_TEMPLATE_MATCHING_MODE MatchingMode
        {
            get { return m_Matching_Mode; }
            set { m_Matching_Mode = value; }
        }

        /// <summary>
        /// Gets the SAD positions.
        /// </summary>
        /// <value>The SAD positions.</value>
        public List<SADPosition> SADPositions
        {
            get { return aPosition; }
        }

        /// <summary>
        /// Execute this Filter
        /// A Debug Statement is written if a concrete Filter is called
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            SADPositions.Clear();
            if (m_BitmapTemplate == null)
            {
                throw new Exception("This filter cannot be used without a given template");
            }

            double VALUE_MAX = 0xFFFFFF;
            double minSAD = VALUE_MAX;

            UnsafeBitmap bitmapTemplate = m_BitmapTemplate;
            UnsafeBitmap bitmapSearch = _bitmap;

            double SAD = 0.0;

            // loop through the search image
            int x = 0;
            int y = 0;

            int j = 0;
            int i = 0;
            int nXTemp = 0;
            int nYTemp = 0;

            int nCount = 0;

            int nValueSearchIMG = 0;
            int nValueTemplate = 0;

            if (m_ScaleDown > 0)
            {
                bitmapTemplate = UnsafeBitmap.CreateScaledClonedBitmap(bitmapTemplate, new Size(bitmapTemplate.Width / m_ScaleDown, bitmapTemplate.Height / m_ScaleDown));
                bitmapSearch = UnsafeBitmap.CreateScaledClonedBitmap(_bitmap, new Size(_bitmap.Width / m_ScaleDown, _bitmap.Height / m_ScaleDown));
            }

            int nTemplateHeight = bitmapTemplate.Height;
            int nTemplateWidth = bitmapTemplate.Width;

            int bitmapHeight = bitmapSearch.Height;
            int bitmapWidth = bitmapSearch.Width;

            int nTemplatePixelCount = nTemplateHeight * nTemplateWidth;
            // loop through the template image
            int nTemplateAverageValue = 0;
            if (m_Matching_Mode == SIMPLE_TEMPLATE_MATCHING_MODE.PRECACHED)
            {
                for (j = 0; j < nTemplateHeight; j++)
                {
                    for (i = 0; i < nTemplateWidth; i++)
                    {
                        nXTemp = x + i;
                        nYTemp = y + j;
                        nValueTemplate = bitmapTemplate[i, j, true].R;
                        nTemplateAverageValue += nValueTemplate;
                    }
                }
                nTemplateAverageValue /= nTemplatePixelCount;
            }

            int nLeft = m_RectangleROI.Left;
            int nTop = m_RectangleROI.Top;

            for (y = nTop; y < bitmapHeight - nTemplateHeight; y++)
            {
                for (x = nLeft; x < bitmapWidth - nTemplateWidth; x++)
                {
                    SAD = 0.0;

                    switch (m_Matching_Mode)
                    {
                        case SIMPLE_TEMPLATE_MATCHING_MODE.MOST_EXACT_COLORED_MATCH:
                            nCount = 0;
                            bool bFoundColor = true;
                            for (j = 0; j < nTemplateHeight; j++)
                            {
                                for (i = 0; i < nTemplateWidth; i++)
                                {
                                    nXTemp = x + i;
                                    nYTemp = y + j;

                                    Color searchImg = bitmapSearch[nXTemp, nYTemp];
                                    Color templateImt = bitmapTemplate[i, j];

                                    if (ColorFunctions.DistanceSquared(searchImg, templateImt) > m_nTolerance)
                                    {
                                        bFoundColor = false;
                                        break;
                                    }
                                    nCount++;
                                }
                                if (!bFoundColor)
                                {
                                    break;
                                }
                            }
                            if (bFoundColor)
                            {
                                SADPosition position = new SADPosition();
                                position.BestRow = y;
                                position.BestCol = x;
                                position.BestSAD = SAD;

                                aPosition.Add(position);
                            }
                            break;

                        case SIMPLE_TEMPLATE_MATCHING_MODE.MOST_EXACT_GRAY_MATCH:
                            nCount = 0;
                            bool bFoundGray = true;
                            for (j = 0; j < nTemplateHeight; j++)
                            {
                                for (i = 0; i < nTemplateWidth; i++)
                                {
                                    nXTemp = x + i;
                                    nYTemp = y + j;

                                    nValueSearchIMG = bitmapSearch[nXTemp, nYTemp, true].R;
                                    nValueTemplate = bitmapTemplate[i, j, true].R;

                                    if (Math.Abs(nValueSearchIMG - nValueTemplate) > (m_nTolerance/10))
                                    {
                                        bFoundGray = false;
                                        break;
                                    }
                                    nCount++;
                                }
                                if (!bFoundGray)
                                {
                                    break;
                                }
                            }
                            if (bFoundGray)
                            {
                                SADPosition position = new SADPosition();
                                position.BestCol = x;
                                position.BestRow = y;
                                position.BestSAD = SAD;

                                aPosition.Add(position);
                            }
                            break;

                        case SIMPLE_TEMPLATE_MATCHING_MODE.PRECACHED:
                            int nPicAverageValue = 0;
                            for (j = 0; j < nTemplateHeight; j++)
                            {
                                for (i = 0; i < nTemplateWidth; i++)
                                {
                                    nXTemp = x + i;
                                    nYTemp = y + j;
                                    nValueSearchIMG = bitmapSearch[nXTemp, nYTemp, true].R;
                                    nPicAverageValue += nValueSearchIMG;
                                }
                            }
                            nPicAverageValue /= nTemplatePixelCount;
                            SAD = Math.Abs(nTemplateAverageValue * nTemplateAverageValue - nPicAverageValue * nPicAverageValue);
                            break;

                        case SIMPLE_TEMPLATE_MATCHING_MODE.NORMAL:
                            for (j = 0; j < nTemplateHeight; j++)
                            {
                                for (i = 0; i < nTemplateWidth; i++)
                                {
                                    nXTemp = x + i;
                                    nYTemp = y + j;
                                    nValueSearchIMG = bitmapSearch[nXTemp, nYTemp, true].R;
                                    nValueTemplate = bitmapTemplate[i, j, true].R;
                                    SAD += Math.Abs(nValueSearchIMG - nValueTemplate);
                                }
                            }
                            break;
                    }

                    if (m_Matching_Mode != SIMPLE_TEMPLATE_MATCHING_MODE.MOST_EXACT_GRAY_MATCH
                        && m_Matching_Mode != SIMPLE_TEMPLATE_MATCHING_MODE.MOST_EXACT_COLORED_MATCH)
                    {
                        //Console.WriteLine(SAD);
                        // save the best found position 
                        if (minSAD > SAD)
                        {
                            SADPosition position = new SADPosition();
                            minSAD = SAD;
                            // give me VALUE_MAX
                            position.BestCol = x;
                            position.BestRow = y;
                            position.BestSAD = SAD;
                            aPosition.Add(position);
                        }
                    }
                }
            }

            if (m_ScaleDown > 0)
            {
                //Scale coordinates back
                //TODO
            }

            return _bitmap;
        }

        /// <summary>
        /// This is used to clone a filter, each filter must implement it's own clone functionality
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new SimpleTemplateMatching(this);
        }
    }
}
